<script setup>
import { onMounted, ref } from 'vue'
import { io } from 'socket.io-client'
import { VideoCamera, Monitor } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import { elConfirm } from '@/uc-element-plus/ele-message'
const localVideo = ref(null)
const remoteVideo = ref(null)
const screenVideo = ref(null)
let ioConnection = ref(false)
let localStream = ref('')
let screenStream = ref('')
const userId = ref(sessionStorage.getItem('userId'))
const message = ref('')
const messageList = ref([])
const userName = ref('')
const userList = ref([])
const isCalling = ref(0)
const isSharing = ref(0)
const videoType = ref('')
let screenSender = null
let peerConnection = null
let dataChannel = null
const customGenerationFunction = () =>
  (Math.random().toString(36) + '0000000000000000000').substring(2, 16)

onMounted(async () => {
  if (!userId.value) {
    userId.value = customGenerationFunction()
    sessionStorage.setItem('userId', userId.value)
  }
  // 用户登录
  try {
    const item = await ElMessageBox.prompt('请输入用户名', '登录', {
      showCancelButton: false,
      showConfirmButton: false,
      inputPlaceholder: '请输入用户名, 按回车确认',
      closeOnClickModal: false,
      showClose: false
    })
    userName.value = item.value || Math.floor(Math.random() * 1000)
    createWs()
  } catch (e) {
    console.log(e)
  }
})

/**
 * 创建websocket连接
 */
let socket = null
const createWs = () => {
  socket = io('http://192.168.137.130:8090', {
    query: {
      userName: userName.value,
      userId: sessionStorage.getItem('userId')
    }
  })
  socket.on('connect', () => {
    console.log('链接成功...')
    ioConnection.value = true
    setLocalStream()
  })
  socket.on('disconnect', () => {})

  // 新增用户
  socket.on('add-user', (data) => {
    userList.value = data
  })

  /**
   * @description 监听收到offer消息, 然后创建应答
   */
  socket.on('offer', async (data) => {
    await peerConnection.setRemoteDescription(data)
    const answer = await peerConnection.createAnswer()
    await peerConnection.setLocalDescription(answer)
    socket.emit('answer', answer)
  })

  /**
   * @description 监听收到answer消息
   */
  socket.on('answer', async (data) => {
    await peerConnection.setRemoteDescription(data)
  })

  /**
   * @description 监听收到candidata消息
   */
  socket.on('ice-candidate', async (data) => {
    await peerConnection.addIceCandidate(data)
  })

  /**
   * @description 监听收到视频消息
   */
  socket.on('call', (data) => {
    videoType.value = data.type
    const message = `是否接受${data.userName}的${data.type === 'video' ? '视频' : '屏幕'}申请?`
    elConfirm({
      message,
      confirm() {
        if (data.type === 'video') {
          isCalling.value = 2
          socket.emit('message', { type: 'isCalling' })
        } else if (data.type === 'screen') {
          isSharing.value = 2
          socket.emit('message', { type: 'isSharing' })
        }
      }
    })
  })

  /**
   * @description 监听收到统一的广播消息
   */
  socket.on('message', (data) => {
    if (data.type === 'isCalling') {
      isCalling.value = 2
    } else if (data.type === 'isSharing') {
      isSharing.value = 2
    } else if (data.type === 'removeScreen') {
      isSharing.value = 0
    }
  })
}

/**
 * @description webrtc RtcDataChnnel发送文本消息
 */
const sendMessage = () => {
  const messageInfo = {
    userId: sessionStorage.getItem('userId'),
    userName: userName.value,
    message: message.value
  }
  messageList.value.push(messageInfo)
  message.value = ''
  try {
    dataChannel.send(JSON.stringify(messageInfo))
  } catch (error) {
    console.log('dataChannel消息发送失败')
  }
}

/**
 * @description 获取本地屏幕的流
 */
const shareScreen = async () => {
  const stream = await navigator.mediaDevices.getDisplayMedia({
    video: true
  })
  const videoTrack = stream.getVideoTracks()[0]
  videoTrack.onended = () => {
    console.log('监听到屏幕流结束')
    isSharing.value = 0
    // p2p连接没有直接的通道通知远端， 需要通过信令服务器通知远端
    socket.emit('message', { type: 'removeScreen' })
  }
  screenStream.value = stream
  screenVideo.value.srcObject = stream
  // 在现有连接中添加屏幕流
  addStreamTrack('screen')
  // 在视频流中添加屏幕流时需要重新发送offer交换媒体流信息
  startCall('screen')
}

/**
 * @description 获取本地音视频媒体流
 */
const setLocalStream = async () => {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true
    })
    localStream.value = stream
    localVideo.value.srcObject = stream
  } catch (err) {
    console.log(err)
  }
  createPeerConnection()
}

/**
 * @description 创建webrtc RTCPeerConnection连接
 */
const createPeerConnection = async () => {
  const config = {
    iceServers: [
      {
        urls: 'stun:stun.l.google.com:19302'
      }
    ]
  }
  peerConnection = new RTCPeerConnection(config)
  // 添加视频流
  addStreamTrack('video')
  // 创建数据通道
  createDatannel()
  // 监听peerConnection的连接状态
  peerConnection.oniceconnectionstatechange = () => {
    console.log('连接状态' + peerConnection.connectionState)
  }
  // 监听candidate并发送
  peerConnection.onicecandidate = (event) => {
    if (event.candidate) {
      socket.emit('ice-candidate', event.candidate)
    }
  }
  // 监听到track事件
  peerConnection.ontrack = (event) => {
    console.log('监听到track事件...')
    console.log(event.track)
    const newStream = new MediaStream()
    newStream.addTrack(event.track)
    // 此处判断不严谨，需要优化，在事件监听里面怎么区分视频和屏幕轨道?
    if (isCalling.value === 2) {
      screenVideo.value.srcObject = newStream
    } else {
      remoteVideo.value.srcObject = newStream
    }
  }

  // 监听到onended事件
  peerConnection.oneended = () => {
    console.log('监听到ended事件')
  }

  // 监听数据通道的消息
  dataChannel.onmessage = (event) => {
    console.log('监听到数据通道的消息...')
    console.log(event.data)
  }
}

/**
 * @description 添加媒体流
 */
const addStreamTrack = (type) => {
  // 如果同时分析视频和屏幕流，需要创建一个合并流
  // const combinedStream = new MediaStream()
  if (type === 'screen') {
    screenStream.value.getTracks().forEach((track) => {
      const customTrack = new MediaStreamTrack(track)
      peerConnection.addTrack(customTrack, screenStream.value)
    })
    // screenStream.value.getTracks().forEach((track) => combinedStream.addTrack(track))
  }
  // 添加本地流到peerConnection中,后续发送给远端
  if (type === 'video') {
    localStream.value.getTracks().forEach((track) => {
      peerConnection.addTrack(track, localStream.value)
    })
    // localStream.value.getTracks().forEach((track) => combinedStream.addTrack(track))
  }
  // combinedStream.getTracks().forEach((track) => peerConnection.addTrack(track, combinedStream))
}

/**
 * 创建datachannel数据通道
 */
const createDatannel = async () => {
  dataChannel = peerConnection.createDataChannel('webrtc-chat')
  peerConnection.ondatachannel = (event) => {
    const channel = event.channel
    channel.onopen = () => {
      console.log('远端已连接')
    }
    // 远端接受消息
    channel.onmessage = (event) => {
      messageList.value.push(JSON.parse(event.data))
    }
  }
  dataChannel.onopen = () => {
    console.log('本地数据通道已连接')
  }
  // 本地端接受事件
  dataChannel.onmessage = (event) => {
    messageList.value.push(JSON.parse(event.data))
  }
}

/**
 * @description 发送offer
 */
const sendOffer = async () => {
  // 创建offer并发送给远端
  const offer = await peerConnection.createOffer()
  // offer添加到本地
  await peerConnection.setLocalDescription(offer)
  // 发送offer给远端
  socket.emit('offer', offer)
}

/**
 * @description 开启视频通话
 */
const startCall = (type) => {
  if (type === 'video') {
    isCalling.value = 1
  } else {
    isSharing.value = 1
  }
  sendOffer()
  socket.emit('call', {
    type,
    userName: userName.value,
    userId: userId.value
  })
}

/**
 * @description 获取视频状态描述 isCalling 0, 1, 2
 */
const getCallType = () => {
  switch (isCalling.value) {
    case 0:
      return '视频'
    case 1:
      return '发送中...'
    case 2:
      return '正在视频...'
    default:
      return '视频'
  }
}

/**
 * @description 获取屏幕分享状态 isShareing 0, 1,2
 */
const getShareType = () => {
  switch (isSharing.value) {
    case 0:
      return '屏幕'
    case 1:
      return '分享中...'
    case 2:
      return '正在分享...'
    default:
      return '视频'
  }
}
</script>
<template>
  <div class="home">
    <div class="header">webrtc音视频和屏幕分享</div>
    <div class="content">
      <div class="left">
        <div class="title">摄像头</div>
        <div class="video-box flex justify-center items-center">
          <div
            v-if="!localStream && ioConnection"
            class="flex flex-col justify-center items-center cursor-pointer"
          >
            <el-icon size="40"><VideoCamera></VideoCamera></el-icon>
          </div>
          <video
            v-show="localStream"
            ref="localVideo"
            autoplay
            muted
            class="w-[100%] h-[100%]"
            style="object-fit: fill"
          ></video>
        </div>
        <div class="video-box">
          <video
            ref="remoteVideo"
            v-show="isCalling == 2"
            autoplay
            muted
            class="w-[100%] h-[100%]"
            style="object-fit: fill"
          ></video>
        </div>
        <div class="video-box" v-if="false">
          <video
            src="../../assets/video.mp4"
            autoplay
            muted
            class="w-[100%] h-[100%]"
            style="object-fit: fill"
          ></video>
        </div>
      </div>
      <div class="center flex flex-col">
        <div class="title">屏幕共享</div>
        <div class="flex-1 flex justify-center items-center h-[0px]">
          <div v-if="ioConnection && isSharing == 0">
            <el-icon size="40"><Monitor></Monitor></el-icon>
          </div>
          <video
            v-show="isSharing !== 0"
            ref="screenVideo"
            autoplay
            muted
            class="screen-video"
          ></video>
        </div>
      </div>
      <div class="right flex flex-col">
        <div class="user-list flex flex-1 flex-col h-[0px]">
          <div class="title">用户列表</div>
          <div class="flex-f1">
            <el-scrollbar>
              <ul>
                <li
                  v-for="item in userList"
                  :key="item.userId"
                  class="flex p-2 border-b-[1px] border-b-[#314253] items-center justify-between"
                >
                  <div>
                    <span>{{ item.userName }}</span>
                    <span v-if="item.userId === userId" class="text-[12px] pl-1 text-[#999]"
                      >[自己]</span
                    >
                  </div>

                  <div>
                    <el-button
                      size="small"
                      type="primary"
                      link
                      @click="startCall('video')"
                      :disabled="isCalling !== 0"
                      v-if="item.userId !== userId"
                    >
                      {{ getCallType(item.userId) }}
                    </el-button>
                    <el-button
                      size="small"
                      type="primary"
                      link
                      :disabled="isSharing !== 0"
                      @click="shareScreen('screen')"
                      v-if="item.userId !== userId && isCalling === 2"
                    >
                      {{ getShareType() }}
                    </el-button>
                  </div>
                </li>
              </ul>
            </el-scrollbar>
          </div>
        </div>
        <div class="chat-list flex flex-1 h-[0px] flex-col">
          <div class="title">聊天记录</div>
          <div class="message-box flex-1 h-[0px]">
            <el-scrollbar>
              <div
                v-for="(item, index) in messageList"
                :key="index"
                :class="['p-[5px]', { 'text-right': item.userId === userId }]"
              >
                {{ item.userName }}：{{ item.message }}
              </div>
            </el-scrollbar>
          </div>
          <div class="chat-box flex p-2" v-if="ioConnection">
            <el-input
              v-model="message"
              @keydown.enter="sendMessage"
              placeholder="请输入聊天信息"
            ></el-input>
            <el-button type="primary" class="ml-[10px]" @click="sendMessage">发送</el-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<style scoped lang="scss">
.home {
  width: 100vw;
  height: 100vh;
  background-color: #1e2831;
  color: #f5f5f5;
  font-size: 14px;
  .header {
    height: 40px;
    width: 100%;
    background-color: rgb(48, 64, 81);
    text-align: center;
    line-height: 40px;
    .back-box {
      position: absolute;
      height: 50px;
      padding: 0 10px;
      cursor: pointer;
    }
  }
  .content {
    height: calc(100% - 40px);
    padding: 5px;
    display: flex;
    .title {
      background-color: rgb(48, 64, 81);
      height: 30px;
      line-height: 30px;
      text-align: center;
      font-size: 12px;
    }
    .left {
      height: 100%;
      width: 320px;
      background-color: #28333d;
      .video-box {
        margin: 5px;
        width: 310px;
        height: 210px;
        object-fit: fill;
        background-color: #1e2831;
      }
    }
    .center {
      margin: 0 5px;
      flex: 1;
      background-color: #28333d;
      .screen-video {
        width: 100%;
        height: auto;
        object-fit: fill;
      }
    }
    .right {
      width: 300px;
      background-color: #28333d;
    }
  }
  .box {
    width: 150px;
    height: 150px;
    border-radius: 4px;
    margin: 0 20px;
    line-height: 150px;
    text-align: center;
    background-color: #28333d;
    cursor: pointer;
    &:hover {
      background-color: rgb(48, 64, 81);
    }
  }
}
</style>
